package org.bsc.maven.reporting.renderer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.factory.ArtifactFactory;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.doxia.sink.Sink;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.MavenProjectBuilder;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.reporting.AbstractMavenReportRenderer;
import org.bsc.maven.reporting.ReportingResolutionListener;
import org.codehaus.plexus.i18n.I18N;


/**
 * 
 * @author Sorrentino
 *
 */
public class DependenciesRenderer extends AbstractMavenReportRenderer {

    private final Locale locale;
    private final ReportingResolutionListener listener;
    private final MavenProject project;
    private final MavenProjectBuilder mavenProjectBuilder;
	private final ArtifactRepository localRepository;
	private final ArtifactFactory factory;
    private final I18N i18n;
    private final Log log;

    /**
     * 
     * @param sink
     * @param project
     * @param mavenProjectBuilder
     * @param locale
     * @param listener
     */
    public DependenciesRenderer(	Sink sink, 
    								MavenProject project, 
    								MavenProjectBuilder mavenProjectBuilder,
    								ArtifactRepository localRepository,
    								ArtifactFactory factory,
    								I18N i18n,
    								Locale locale, 
    								ReportingResolutionListener listener,
    								Log log )
    {
        super( sink );

        this.project = project;
        this.locale = locale;
        this.listener = listener;
        this.mavenProjectBuilder = mavenProjectBuilder;
        this.localRepository = localRepository;
        this.i18n 	= i18n;
        this.factory = factory;
        this.log = log;
    }

    public String getTitle()
    {
        return getReportString( "report.dependencies.title" );
    }

    public void renderBody()
    {
        // Dependencies report
    	
        List<?> dependencies = listener.getRootNode().getChildren();

        if ( dependencies.isEmpty() )
        {
            startSection( getTitle() );

            // TODO: should the report just be excluded?
            paragraph( getReportString( "report.dependencies.nolist" ) );

            endSection();

            return;
        }

        startSection( getTitle() );
        
        String groupId = getReportString( "report.dependencies.column.groupId" );
        String artifactId = getReportString( "report.dependencies.column.artifactId" );
        String version = getReportString( "report.dependencies.column.version" );
        String classifier = getReportString( "report.dependencies.column.classifier" );
        String type = getReportString( "report.dependencies.column.type" );
        String optional = getReportString( "report.dependencies.column.optional" );
        String[] tableHeader = new String[]{groupId, artifactId, version, classifier, type, optional};

        // collect dependencies by scope
        Map dependenciesByScope = getDependenciesByScope( dependencies );

        renderDependenciesForScope( Artifact.SCOPE_COMPILE,
                                    (List) dependenciesByScope.get( Artifact.SCOPE_COMPILE ), tableHeader );
        renderDependenciesForScope( Artifact.SCOPE_RUNTIME,
                                    (List) dependenciesByScope.get( Artifact.SCOPE_RUNTIME ), tableHeader );
        renderDependenciesForScope( Artifact.SCOPE_TEST, (List) dependenciesByScope.get( Artifact.SCOPE_TEST ),
                                    tableHeader );
        renderDependenciesForScope( Artifact.SCOPE_PROVIDED,
                                    (List) dependenciesByScope.get( Artifact.SCOPE_PROVIDED ), tableHeader );
        renderDependenciesForScope( Artifact.SCOPE_SYSTEM, (List) dependenciesByScope.get( Artifact.SCOPE_SYSTEM ),
                                    tableHeader );

        endSection();

        // Transitive dependencies
        List artifacts = new ArrayList( listener.getArtifacts() );
        artifacts.removeAll( dependencies );

        startSection( getReportString( "report.dependencies.transitive.title" ) );

        if ( artifacts.isEmpty() )
        {
            paragraph( getReportString( "report.dependencies.transitive.nolist" ) );
        }
        else
        {
            paragraph( getReportString( "report.dependencies.transitive.intro" ) );

            dependenciesByScope = getDependenciesByScope( artifacts );

            renderDependenciesForScope( Artifact.SCOPE_COMPILE,
                                        (List) dependenciesByScope.get( Artifact.SCOPE_COMPILE ), tableHeader );
            renderDependenciesForScope( Artifact.SCOPE_RUNTIME,
                                        (List) dependenciesByScope.get( Artifact.SCOPE_RUNTIME ), tableHeader );
            renderDependenciesForScope( Artifact.SCOPE_TEST, (List) dependenciesByScope.get( Artifact.SCOPE_TEST ),
                                        tableHeader );
            renderDependenciesForScope( Artifact.SCOPE_PROVIDED,
                                        (List) dependenciesByScope.get( Artifact.SCOPE_PROVIDED ), tableHeader );
            renderDependenciesForScope( Artifact.SCOPE_SYSTEM,
                                        (List) dependenciesByScope.get( Artifact.SCOPE_SYSTEM ), tableHeader );
        }

        endSection();

        //for Dependencies Graph
        startSection( getReportString( "report.dependencies.graph.title" ) );

        //for Dependencies Graph Tree
        startSection( getReportString( "report.dependencies.graph.tree.title" ) );

        sink.lineBreak();

        sink.paragraph();
        //sink.list();
        printDependencyListing( listener.getRootNode(), false );
        //sink.list_();
        sink.paragraph_();
        endSection();

        //for Artifact Descriptions / URLs
        startSection( getReportString( "report.dependencies.file.details.title" ) );
        printDescriptionsAndURLs( listener.getRootNode() );
        endSection();

        endSection();
    }

    private Map getDependenciesByScope( List dependencies )
    {
        Map dependenciesByScope = new HashMap();
        for ( Iterator i = dependencies.iterator(); i.hasNext(); )
        {
            ReportingResolutionListener.Node node = (ReportingResolutionListener.Node) i.next();
            Artifact artifact = node.getArtifact();

            List multiValue = (List) dependenciesByScope.get( artifact.getScope() );
            if ( multiValue == null )
            {
                multiValue = new ArrayList();
            }
            multiValue.add( artifact );
            dependenciesByScope.put( artifact.getScope(), multiValue );
        }
        return dependenciesByScope;
    }

    private void renderDependenciesForScope( String scope, List artifacts, String[] tableHeader )
    {
        if ( artifacts != null )
        {
            // can't use straight artifact comparison because we want optional last
            Collections.sort( artifacts, getArtifactComparator() );

            startSection( scope );

            paragraph( getReportString( "report.dependencies.intro." + scope ) );
            startTable();
            tableHeader( tableHeader );

            for ( Iterator iterator = artifacts.iterator(); iterator.hasNext(); )
            {
                Artifact artifact = (Artifact) iterator.next();
                tableRow( getArtifactRow( artifact ) );
            }
            endTable();

            endSection();
        }
    }

    private Comparator getArtifactComparator()
    {
        return new Comparator()
        {
            public int compare( Object o1, Object o2 )
            {
                Artifact a1 = (Artifact) o1;
                Artifact a2 = (Artifact) o2;

                // put optional last
                if ( a1.isOptional() && !a2.isOptional() )
                {
                    return +1;
                }
                else if ( !a1.isOptional() && a2.isOptional() )
                {
                    return -1;
                }
                else
                {
                    return a1.compareTo( a2 );
                }
            }
        };
    }

    private String[] getArtifactRow( Artifact artifact )
    {
        return new String[]{artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion(),
            artifact.getClassifier(), artifact.getType(), artifact.isOptional() ? "(optional)" : " "};
    }

    private void printDependencyListing( ReportingResolutionListener.Node node, boolean printRoot )
    {
        Artifact artifact = node.getArtifact();
        String id = artifact.getDependencyConflictId();

        sink.listItem();
        sink.paragraph();

        if( printRoot ) {
	        sink.link( "#" + id );
	        sink.text( id );
	        sink.link_();
        }

        if ( !node.getChildren().isEmpty() )
        {
            sink.list();
            for ( Iterator deps = node.getChildren().iterator(); deps.hasNext(); )
            {
                ReportingResolutionListener.Node dep = (ReportingResolutionListener.Node) deps.next();
                printDependencyListing( dep, true );
            }
            sink.list_();
        }

        sink.paragraph_();
        sink.listItem_();
    }

    private void printDescriptionsAndURLs( ReportingResolutionListener.Node node )
    {
        Artifact artifact = node.getArtifact();
        String id = artifact.getDependencyConflictId();

        if ( !Artifact.SCOPE_SYSTEM.equals( artifact.getScope() ) )
        {
            try
            {
                MavenProject artifactProject = getMavenProjectFromRepository( artifact, localRepository );
                String artifactDescription = artifactProject.getDescription();
                String artifactUrl = artifactProject.getUrl();
                String artifactName = artifactProject.getName();

                sink.paragraph();
                sink.anchor( id );
                sink.bold();
                sink.text( artifactName );
                sink.bold_();
                sink.anchor_();
                sink.paragraph_();

                if ( artifactDescription != null )
                {
                    sink.paragraph();
                    sink.text( artifactDescription );
                    sink.paragraph_();
                }

                if ( artifactUrl != null )
                {
                    sink.paragraph();
                    sink.link( artifactUrl );
                    sink.text( artifactUrl );
                    sink.link_();
                    sink.paragraph_();
                }
            }
            catch ( ProjectBuildingException e )
            {
                log.debug( e );
            }

            for ( Iterator deps = node.getChildren().iterator(); deps.hasNext(); )
            {
                ReportingResolutionListener.Node dep = (ReportingResolutionListener.Node) deps.next();
                printDescriptionsAndURLs( dep );
            }
        }
        else
        {
            sink.paragraph();
            sink.anchor( id );
            sink.bold();
            sink.text( id );
            sink.bold_();
            sink.anchor_();
            sink.paragraph_();

            sink.paragraph();
            sink.text( artifact.getFile().toString() );
            sink.paragraph_();
        }
    }

    /**
     * Get the <code>Maven project</code> from the repository depending
     * the <code>Artifact</code> given.
     *
     * @param artifact an artifact
     * @return the Maven project for the given artifact
     * @throws org.apache.maven.project.ProjectBuildingException
     *          if any
     */
    private MavenProject getMavenProjectFromRepository( Artifact artifact, ArtifactRepository localRepository )
        throws ProjectBuildingException
    {
        Artifact projectArtifact = artifact;

        boolean allowStubModel = false;
        if ( !"pom".equals( artifact.getType() ) )
        {
            projectArtifact = factory.createProjectArtifact( artifact.getGroupId(), artifact.getArtifactId(),
                                                             artifact.getVersion(), artifact.getScope() );
            allowStubModel = true;
        }

        // TODO: we should use the MavenMetadataSource instead
        return mavenProjectBuilder.buildFromRepository( projectArtifact, project.getRemoteArtifactRepositories(),
                                                        localRepository, allowStubModel );
    }

    private String getReportString( String key )
    {
        return i18n.getString( "project-info-report", locale, key );
    }

}
